package com.gitee.sop.notify.service;

import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.support.LambdaService;
import com.gitee.httphelper.HttpHelper;
import com.gitee.httphelper.result.ResponseResult;
import com.gitee.sop.notify.dao.entity.NotifyInfo;
import com.gitee.sop.notify.dao.mapper.NotifyInfoMapper;
import com.gitee.sop.notify.enums.NotifyStatusEnum;
import com.gitee.sop.notify.service.bo.NotifyBO;
import com.gitee.sop.sdk.sign.SignUtil;
import com.gitee.sop.sdk.sign.SopSignException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;


/**
 * 回调业务逻辑处理
 *
 * @author 六如
 */
@Service
@Slf4j
public class NotifyBizService implements LambdaService<NotifyInfo, NotifyInfoMapper> {

    // 对应第1，2，3...次尝试
    // 即1分钟后进行第一次尝试，如果失败，5分钟后进行第二次尝试
    @Value("${sop.notify.time-level:1m,5m,10m,30m,1h,2h,5h}")
    private String timeLevel;

    @Autowired
    private IsvService isvService;

    /**
     * 第一次发送
     *
     * @param notifyBO 回调内容
     * @return 返回回调id
     * @throws SopSignException 异常处理
     */
    public Long notify(NotifyBO notifyBO) throws SopSignException {
        NotifyInfo notifyInfo = buildRecord(notifyBO);
        return doNotify(notifyBO, notifyInfo);
    }

    public void notifyImmediately(Long notifyId) throws SopSignException {
        NotifyInfo notifyInfo = this.getById(notifyId);
        String content = notifyInfo.getContent();
        NotifyBO notifyBO = JSON.parseObject(content, NotifyBO.class);
        // 发送请求
        doNotify(notifyBO, notifyInfo);
    }


    /**
     * 重试
     *
     * @param now 当前时间
     */
    public void retry(LocalDateTime now) {
        LocalDateTime nextTime = now.withSecond(0).withNano(0);
        List<NotifyInfo> list = this.query()
                .eq(NotifyInfo::getNextSendTime, nextTime)
                .eq(NotifyInfo::getNotifyStatus, NotifyStatusEnum.SEND_FAIL.getValue())
                .list();
        if (list.isEmpty()) {
            log.info("[notify]无重试记录");
            return;
        }

        for (NotifyInfo notifyInfo : list) {
            retry(notifyInfo);
        }
    }

    private void retry(NotifyInfo notifyInfo) {
        String content = notifyInfo.getContent();
        NotifyBO notifyBO = JSON.parseObject(content, NotifyBO.class);
        try {
            log.info("[notify]开始重试, notifyId={}", notifyInfo.getId());
            if (Objects.equals(notifyInfo.getNotifyStatus(), NotifyStatusEnum.RETRY_OVER.getValue())) {
                log.warn("重试次数已用尽, notifyId={}", notifyInfo.getId());
                return;
            }
            // 发送请求
            doNotify(notifyBO, notifyInfo);
        } catch (SopSignException e) {
            log.error("[notify]重试签名错误，notifyId={}", notifyInfo.getId(), e);
            throw new RuntimeException("重试失败，签名错误");
        }
    }

    /**
     * 构建下一次重试时间
     *
     * @param currentSendCnt 当前发送次数
     * @return 返回null表示重试次数用完
     */
    private LocalDateTime buildNextSendTime(Integer currentSendCnt) {
        String[] split = timeLevel.split(",");
        if (currentSendCnt >= split.length) {
            return null;
        }
        // 1m
        String exp = split[currentSendCnt - 1];
        // 秒,毫秒归零f
        LocalDateTime time = LocalDateTime.now().withSecond(0).withNano(0);
        // 最后一个字符，如：m,h,d
        char ch = exp.charAt(exp.length() - 1);
        int value = NumberUtils.toInt(exp.substring(0, exp.length() - 1));
        switch (String.valueOf(ch).toLowerCase()) {
            case "m":
                return time.plusMinutes(value);
            case "h":
                return time.plusHours(value);
            case "d":
                return time.plusDays(value);
            default:
                return null;
        }
    }


    private Long doNotify(NotifyBO notifyBO, NotifyInfo notifyInfo) throws SopSignException {
        notifyInfo.setSendCnt(notifyInfo.getSendCnt() + 1);
        notifyInfo.setLastSendTime(LocalDateTime.now());
        notifyInfo.setNotifyUrl(buildNotifyUrl(notifyBO, notifyInfo));

        String notifyUrl = notifyInfo.getNotifyUrl();
        // 构建请求参数
        Map<String, String> params = buildParams(notifyBO);
        try {
            if (StringUtils.isBlank(notifyUrl)) {
                throw new RuntimeException("回调接口不能为空");
            }

            String json = JSON.toJSONString(params);
            log.info("发送回调请求，notifyUrl={}, content={}", notifyUrl, json);
            ResponseResult responseResult = HttpHelper.postJson(notifyUrl, json)
                    .execute();

            // 这里判断收到200认为请求成功
            int status = responseResult.getStatus();
            String resultContent = responseResult.asString();
            notifyInfo.setResultContent(resultContent);
            if (status == HttpStatus.SC_OK) {
                // 更新状态
                notifyInfo.setNotifyStatus(NotifyStatusEnum.SEND_SUCCESS.getValue());
                notifyInfo.setErrorMsg("");
            } else {
                // 回调失败
                log.error("回调状态非200:{}, result={}", status, resultContent);
                throw new RuntimeException(resultContent);
            }
        } catch (Exception e) {
            log.error("回调请求失败, notifyUrl={}, params={}, notifyBO={}", notifyUrl, params, notifyBO, e);
            notifyInfo.setNotifyStatus(NotifyStatusEnum.SEND_FAIL.getValue());
            notifyInfo.setErrorMsg(e.getMessage());

            LocalDateTime nextSendTime = buildNextSendTime(notifyInfo.getSendCnt());
            notifyInfo.setNextSendTime(nextSendTime);

            if (nextSendTime == null) {
                log.error("回调请求次数达到上线, notifyUrl={}, params={}", notifyUrl, params);
                notifyInfo.setNotifyStatus(NotifyStatusEnum.RETRY_OVER.getValue());
            }
        }

        this.saveOrUpdate(notifyInfo);

        return notifyInfo.getId();
    }

    private Map<String, String> buildParams(NotifyBO notifyBO) throws SopSignException {
        // 公共请求参数
        Map<String, String> params = new HashMap<>();
        String appId = notifyBO.getAppId();
        params.put("app_id", appId);
        params.put("method", notifyBO.getApiName());
        params.put("format", "json");
        params.put("charset", notifyBO.getCharset());
        params.put("sign_type", "RSA2");
        params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        params.put("version", notifyBO.getVersion());

        // 业务参数
        Map<String, Object> bizContent = notifyBO.getBizParams();

        params.put("biz_content", JSON.toJSONString(bizContent));
        String content = SignUtil.getSignContent(params);

        String privateKey = isvService.getPrivatePlatformKey(appId);
        String sign = SignUtil.rsa256Sign(content, privateKey, notifyBO.getCharset());
        params.put("sign", sign);

        return params;
    }

    private String buildNotifyUrl(NotifyBO notifyBO, NotifyInfo notifyInfo) {
        String savedUrl = notifyInfo.getNotifyUrl();
        if (StringUtils.isNotBlank(savedUrl)) {
            return savedUrl;
        }
        String notifyUrl = notifyBO.getNotifyUrl();
        if (StringUtils.isBlank(notifyUrl)) {
            notifyUrl = isvService.getNotifyUrl(notifyBO.getAppId());
        }
        return notifyUrl;
    }

    private NotifyInfo buildRecord(NotifyBO notifyBO) {
        NotifyInfo notifyInfo = new NotifyInfo();
        notifyInfo.setAppId(notifyBO.getAppId());
        notifyInfo.setApiName(notifyBO.getApiName());
        notifyInfo.setApiVersion(notifyBO.getVersion());
        notifyInfo.setSendCnt(0);
        notifyInfo.setContent(JSON.toJSONString(notifyBO));
        notifyInfo.setNotifyStatus(0);
        notifyInfo.setErrorMsg("");
        notifyInfo.setRemark(notifyBO.getRemark());
        notifyInfo.setAddTime(LocalDateTime.now());
        notifyInfo.setUpdateTime(LocalDateTime.now());
        notifyInfo.setAddBy(0L);
        notifyInfo.setUpdateBy(0L);

        return notifyInfo;
    }


}
